package org.magnerfuller.imtoolkit.server.main;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;

import org.magnerfuller.imtoolkit.server.connection.handler.ConnectionHandler;
import org.magnerfuller.imtoolkit.server.connection.handler.ConnectionHandlerDescription;
import org.magnerfuller.imtoolkit.server.connection.handler.ConnectionHandlerFactory;
import org.magnerfuller.imtoolkit.server.connection.handler.SocketConnectionHandler;
import org.magnerfuller.imtoolkit.server.connection.manager.ConnectionManager;
import org.magnerfuller.imtoolkit.server.connection.manager.event.ConnectionManagerEvent;
import org.magnerfuller.imtoolkit.server.connection.manager.event.listener.ConnectionManagerListener;
import org.magnerfuller.imtoolkit.server.modules.ModuleLoader;
import org.magnerfuller.imtoolkit.server.modules.event.ModuleLoaderEvent;
import org.magnerfuller.imtoolkit.server.modules.event.listener.ModuleLoaderListener;
import org.magnerfuller.imtoolkit.server.service.ExternalServiceBackend;
import org.magnerfuller.imtoolkit.server.service.ManagementConsoleService;
import org.magnerfuller.imtoolkit.server.service.ServiceDescription;
import org.magnerfuller.imtoolkit.server.service.ServiceFactory;
import org.magnerfuller.imtoolkit.server.session.manager.SessionManager;
import org.magnerfuller.imtoolkit.server.status.ServerStatus;
import org.magnerfuller.imtoolkit.util.OptionsParser;
import org.magnerfuller.imtoolkit.util.Stanza;
import org.magnerfuller.imtoolkit.util.StanzaFileParser;
import org.magnerfuller.imtoolkit.util.StanzaParse;
import org.magnerfuller.imtoolkit.util.exception.OptionParserException;



public class ImToolkitServerMain implements ConnectionManagerListener, ModuleLoaderListener {

  /** sessionManager -- Manages sessions in progress. */
  private SessionManager sessionManager;

  /** connectionManager -- Manages incoming connections */
  private ConnectionManager connectionManager;
  
  private ModuleLoader moduleLoader;

  public ImToolkitServerMain() {
    sessionManager = new SessionManager();
    connectionManager = new ConnectionManager();
    moduleLoader = new ModuleLoader();
    moduleLoader.addModuleLoaderListener(this);
  }
  
  public void loadConnectionHandlerModules() {
    ConnectionHandlerFactory.getInstance().addHandlerModule(SocketConnectionHandler.class);
  }
  
  public void loadServiceModules() {
    ServiceFactory.getInstance().addServiceModule(ExternalServiceBackend.class);
    ServiceFactory.getInstance().addServiceModule(ManagementConsoleService.class);
  }
  
  public void deployConfig(StanzaParse config) {
    Iterator<Stanza> stanzas = config.getStanzas().iterator();
    
    while(stanzas.hasNext()) {
      Stanza stanza = stanzas.next();
      
      if(stanza.getTitle().equals("service")) {
        ServiceDescription serviceDesc = new ServiceDescription(stanza);
        ConnectionHandlerDescription handlerDesc = new ConnectionHandlerDescription(stanza);
        
        ConnectionHandler handler = 
          ConnectionHandlerFactory.getInstance().createConnectionHandler(handlerDesc, serviceDesc);
        connectionManager.manageConnection(handler);
      } else if(stanza.getTitle().equals("plugins")) {
        Iterator<String> pluginNames = stanza.keySet().iterator();
        while(pluginNames.hasNext()) {
          String pluginName = pluginNames.next();
          try {
            moduleLoader.loadModule(new File(stanza.get(pluginName)), "plugin_info");
          } catch (Exception e) {
            e.printStackTrace();
          }
        }
      } else {
        System.err.println("Ignoring unknown config stanza title: " + stanza.getTitle());
      }
    }
  }
  
  public void runServer(String[] args) {
    loadConnectionHandlerModules();
    loadServiceModules();
    
    connectionManager.addConnectionManagerListener(this);
    
    parseArgs(args);
  }

  public static void main(String[] args) {
    ImToolkitServerMain mainThread = new  ImToolkitServerMain();
    
    ServerStatus.getInstance().setSessionManager(mainThread.sessionManager);
    ServerStatus.getInstance().setConnectionManager(mainThread.connectionManager);
    
    mainThread.runServer(args);
  }

  public void connectionManagerEventOccurred(ConnectionManagerEvent e) {
    // Handle this event from the ConnectionManager.
    // This involves getting the connection info, getting a session,
    // and passing it off to the SessionManager.
    
    sessionManager.manageSession(e.getSession());
    e.getSession().start();
  }
  
  public void moduleLoaderEventOccurred(ModuleLoaderEvent e) {
    if(e.getType() == ModuleLoaderEvent.MODULE_WITH_PROPS_LOAD) {
      StanzaParse props = e.getModuleProps();
      
      Iterator<Stanza> stanzas = props.getStanzas().iterator();
      
      while(stanzas.hasNext()) {
        handleModuleConfigStanza(stanzas.next());
      }
    }
  }
  
  private void handleModuleConfigStanza(Stanza stanza) {
    if(stanza.getTitle().equals("connection")) {
      String moduleName = stanza.get("name");
      String moduleClass = stanza.get("class");
      
      if(moduleName != null && moduleClass != null) {
        try {
          ConnectionHandlerFactory.getInstance().addHandlerModule(moduleLoader.loadClass(moduleClass), moduleName);
        } catch (Exception e) {
          System.err.println("Failed to load class " + moduleClass + " for module " + moduleName + "!");
          e.printStackTrace();
        }
      }
      else {
        System.err.println("Stanza has no module name or class name!");
      }
    }
    else if(stanza.getTitle().equals("service")) {
      String moduleName = stanza.get("name");
      String moduleClass = stanza.get("class");
      
      if(moduleName != null && moduleClass != null) {
        try {
          ServiceFactory.getInstance().addServiceModule(moduleLoader.loadClass(moduleClass), moduleName);
        } catch (Exception e) {
          System.err.println("Failed to load class " + moduleClass + " for module " + moduleName + "!");
          e.printStackTrace();
        }
      }
      else {
        System.err.println("Stanza has no module name or class name!");
      }
    }
  }
  
  private StanzaParse parseFile(String filename) {
    StanzaParse parse = null;
    FileReader file = null;
    try {
      StanzaFileParser parser = new StanzaFileParser();
      file = new FileReader(filename);
      parse = parser.parseFile(file);
    } catch(IOException e) {
      System.err.println(e);
      parse = null; 
    } catch(Exception e) {
      System.err.println(e);
      parse = null;
    } finally {
      try {file.close();} catch(Exception e) {}
    }
    
    return parse;
  }
  
  private boolean parseArgs(String[] args) {
    CmdParser parser = new CmdParser();
    Map<String, String> props;
    try {
      props = parser.parse(args);
    } catch(OptionParserException e) {
      System.err.println(e);
      return false;
    }
    
    Iterator<String> keys = props.keySet().iterator();
    while(keys.hasNext()) {
      String key = keys.next();
      if(key.equals("file")) {
        // Parse a config file.
        StanzaParse parse = parseFile(props.get(key));
        if(parse != null) {
          System.out.println(parse.toString());
          deployConfig(parse);
        }
      }
      else if(key.equals("plugins")) {
        try {
          moduleLoader.loadModule(new File(props.get(key)), "plugin_info");
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    }
    return true;
  }
  
  private class CmdParser extends OptionsParser {
    
    public boolean isValidKey(String key) {
      boolean valid;
      if(key.equals("file")) {
        valid = true;
      }
      else if(key.equals("plugins")) {
        valid = true;
      }
      else {
        valid = false;
      }
      
      return valid;
    }
    
    public boolean isValidValue(String key, String value) {
      return true;
    }
    
    public boolean hasValue(String key) {
      boolean valid;
      
      if(key.equals("file")) {
        valid = true;
      }
      else if(key.equals("plugins")) {
        valid = true;
      }
      else {
        valid = false;
      }
      
      return valid;
    }
  }

}


